/+
Module "chk_struc_ok_for_fetch" defines a method to compile time check 
structures for structure fetch.

Library dbapi - Simple database api for the D programming language.

Copyright (C) 2007 Myron Alexander (myron.alexander...gmail.com)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+/

module dbapi.internal.chk_struc_ok_for_fetch;

import std.metastrings : ToString;
import std.traits : FieldTypeTuple;

import dbapi.base : tNullFlag;

//------------------------------------------------------------------------------
// Define helper templates for manipulating the type strings.
//
// These helper templates rely on the specific output of .stringof. If that
// changes, then the strings returned will be incorrect.
//------------------------------------------------------------------------------

template structname (S) {
   static const string structname = S.stringof[0..$-1];
}

template fieldname (S, uint i) {
   static const string fieldname = S.tupleof[i].stringof[S.stringof.length+1+2..$];
}

template fqfieldname (S, uint i) {
   static const string fqfieldname =
      structname!(S)~"."~S.tupleof[i].stringof[S.stringof.length+1+2..$];
}

template typeofNullFlagAr (uint size) {
   static const char[] typeofNullFlagAr = "tNullFlag["~ToString!(size)~"]";
}

/**
 * Compile time check that structure field types match supported types for
 * structure fetch.
 *
 * Supported types :-
 *    int (int32), long (int64), char[] (utf-8), void[] (blob), float, double.
 *
 * Unsupported types :-
 *    wchar[] (utf-16)
 *    void*   (null)
 *
 * It does not make sense to support null as a value return type so it will
 * never be supported. The utf16 type is not supported at this time as I do
 * not know how to support it. Only partial support for utf16 in the system.
 *
 * The procedure also checks the struct for as many deviations from convention
 * as is possible:
 *
 * - Must have at least one data field
 * - A data field must not be named 'nullFlag'
 * - A data field must not be declared with the null flag array type (tNullFlag)
 * - If a null flag array is declared:
 *   - there must be at least one data field before it
 *   - it must be the last field
 *   - it must be named 'nullFlag'
 *   - it must be a static array with one member per data field
 *   - the underlying type must be tNullFlag
 *
 * The template defines a string that is to be used by mixin so the proper way
 * to call this method is:
 *    mixin (chkStrucOkForFetch!("<structure name>"));
 *
 * The mixin method was chosen to reduce the binary size as all other methods
 * placed bytes in the code or data segments of the binary.
 *
 * Thanks to Jari-Matti Makela who pointed out the ".stringof" properties.
 */
template chkStrucOkForFetch (char[] S) {

   const chkStrucOkForFetch = `{

   /* An alias is used instead of 'static const FTT = FieldTypeTuple!(S)' as the
    * generated code would waste unused space. The alias does not impact
    * performance and does not result in compiled code.
    */
   alias FieldTypeTuple!(`~S~`) FTT; 

   /* Ensure that the structure has at least one field.
    */
   static if (0 == FTT.length) {
      pragma (msg, 
         "Error: "
         "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
         "structure must have at least one field; this structure has none."
      );
      debug (StatAssertsDontFail) {} else {
         pragma (msg, "");
         static assert (0, "aborting compilation");
      }
   }

   /* Iterate through each of the structure fields, starting with the first
    * declared field, and validate the field types.
    */
   foreach (i, f; FTT) {

      /* NOTE: Order of these tests is relevant.
       */
      
      /* Detect the null flag array declaration and validate it.
       *
       * 1. The null flag array must be the last field so we only look for it
       *    at the last field.
       * 2. The 'is typeof ...' tests for two conditions at the same time.
       *    - The type of 'f' must be an array or it will not be syntactically
       *      correct and the is() will fail.
       *    - The type of the first element is compared with tNullFlag.
       *
       * This evaluation is only possible because the typedef statement creates
       * a distinct type, it is also the reason for the tNullFlag type.
       */
      static if ((i == FTT.length-1) && (is (typeof (f[0]) == tNullFlag))) {

         /* Ensure that there is at least one data field before the null flag
          * array.
          */
         static if (0 == i) {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
               "only field declared in the structure is the null flag array. "
               "The structure must have at least one data field; this "
               "structure has none."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

         /* The null flag array must be a static array; the size must be the
          * number of data fields.
          */
         static if (!is (typeof (f) == tNullFlag[FTT.length-1])) {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
               "last field of the structure is a null flag array but the array "
               "is incorrectly declared; it is either dynamic, or the size is "
               "incorrect. The null flag array must be a static array declared "
               "as such: 'tNullFlag[<num data fields>] nullFlag'. For this "
               "structure, assuming the data fields are declared correctly, "
               "the null flag array should be defined as '"~
               typeofNullFlagAr!(FTT.length-1)~" nullFlag;'."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

         /* The null flag array must be named 'nullFlag'.
          */
         static if (fieldname!(`~S~`,i) != "nullFlag") {
            pragma (msg,
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
               "last field of the structure, '"~fieldname!(`~S~`,i)~"', matches "
               "the null flag array type signature but is not named 'nullFlag'. "
               "By convention, the null flag array must be declared as such: "
               "'tNullFlag[<num data fields>] nullFlag'. For this structure, "
               "assuming the data fields are declared correctly, the null flag "
               "array should be defined as '"~typeofNullFlagAr!(FTT.length-1)~
               " nullFlag;'."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

      } else {

         /* Validate all the data fields. Data fields will be populated with
          * data and exclude the null flag array.
          */

         /* Validate that a data field is not declared with tNullFlag.
          */
         static if (is (typeof(f) == tNullFlag)) {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
               "field '"~fieldname!(`~S~`,i)~"' is declared with the null flag "
               "array base type 'tNullFlag'. The type 'tNullFlag' is a marker "
               "type and may only be used to declare the null flag array. "
               "By convention, the null flag array must be declared as the last "
               "field of the structure with the following declaration: "
               "'tNullFlag[<num data fields>] nullFlag'. For this structure, "
               "the null flag array would be declared as '"~
               typeofNullFlagAr!(FTT.length-1)~" nullFlag;'."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

         /* Validate that a data field is not declared with tNullFlag.
          */
         static if (is (typeof(f[0]) == tNullFlag)) {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. The "
               "field '"~fieldname!(`~S~`,i)~"' is declared with the null flag "
               "array base type 'tNullFlag'. The type 'tNullFlag' is a marker "
               "type and may only be used to declare the null flag array. "
               "By convention, the null flag array must be declared as the last "
               "field of the structure with the following declaration: "
               "'tNullFlag[<num data fields>] nullFlag'. For this structure, "
               "the null flag array would be declared as '"~
               typeofNullFlagAr!(FTT.length-1)~" nullFlag;'."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

         /* Validate that a data field declared with one of the supported data
          * types.
          */
         static if (
            !is (typeof (f) == int)    && // int32
            !is (typeof (f) == long)   && // int64
            !is (typeof (f) == char[]) && // utf-8
            !is (typeof (f) == void[]) && // blob
            !is (typeof (f) == float)  &&
            !is (typeof (f) == double)
            ) {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. Data "
               "field '"~fieldname!(`~S~`,i)~"', type '"~f.stringof~"', is not "
               "supported by fetchstruct. The only types supported are: int, "
               "long, char[], void[], float, double."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }

         /* A data field may not be named 'nullFlag'.
          */
         static if (fieldname!(`~S~`,i) == "nullFlag") {
            pragma (msg, 
               "Error: "
               "Structure '"~structname!(`~S~`)~"' invalid for fetchstruct. A data "
               "field may not be named 'nullFlag'."
            );
            debug (StatAssertsDontFail) {} else {
               pragma (msg, "");
               static assert (0, "aborting compilation");
            }
         }
      }
   }
 }`;
}
